/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include <X11/extensions/sync.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/Xrandr.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../toolkit/xqueue.h"
#include "../toolkit/display.h"
#include "../toolkit/keyboard.h"
#include "../toolkit/xml.h"
#include "../toolkit/prop.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "wm.h"
#include "event.h"
#include "debug.h"
#include "focus.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "frame.h"
#include "actions.h"
#include "keyboard.h"
#include "screen.h"
#include "mouse.h"
#include "grab.h"
#include "focus_cycle.h"
struct focus_delay_data {
	struct wm_client *client;
	Time time;
	gulong serial;
};
struct serial_range {
	gulong start;
	gulong end;
};
struct skip_property_change {
	Window window;
	Atom prop;
};
Time event_last_user_time = CurrentTime;
static Time event_curtime = CurrentTime;
static Time event_sourcetime = CurrentTime;
static gulong event_curserial;
static gboolean focus_left_screen = FALSE;
static gboolean waiting_for_focusin = FALSE;
static GSList *ignore_serials = NULL;
static guint focus_delay_timeout_id = 0;
static struct wm_client *focus_delay_timeout_client = NULL;
static guint unfocus_delay_timeout_id = 0;
static struct wm_client *unfocus_delay_timeout_client = NULL;

static Window event_get_window(XEvent *e)
{
	Window window;
	switch (e->type) {
	case SelectionClear:
		window = DefaultRootWindow(t_display);
		break;
	case CreateNotify:
		window = e->xcreatewindow.window;
		break;
	case MapRequest:
		window = e->xmaprequest.window;
		break;
	case MapNotify:
		window = e->xmap.window;
		break;
	case UnmapNotify:
		window = e->xunmap.window;
		break;
	case DestroyNotify:
		window = e->xdestroywindow.window;
		break;
	case ConfigureRequest:
		window = e->xconfigurerequest.window;
		break;
	case ConfigureNotify:
		window = e->xconfigure.window;
		break;
	default:
		if(t_display_extension_xkb && e->type == t_display_extension_xkb_basep) {
			switch (((XkbAnyEvent *) e)->xkb_type) {
			case XkbBellNotify:
				window = ((XkbBellNotifyEvent *) e)->window;
			default:
				window = None;
			}
		} else if(t_display_extension_sync && e->type == t_display_extension_sync_basep + XSyncAlarmNotify) {
			window = None;
		} else
			window = e->xany.window;
	}
	return window;
}

static inline Time event_get_timestamp(const XEvent *e)
{
	Time t = CurrentTime;
	switch (e->type) {
	case ButtonPress:
	case ButtonRelease:
		t = e->xbutton.time;
		break;
	case KeyPress:
		t = e->xkey.time;
		break;
	case KeyRelease:
		t = e->xkey.time;
		break;
	case MotionNotify:
		t = e->xmotion.time;
		break;
	case PropertyNotify:
		t = e->xproperty.time;
		break;
	case EnterNotify:
	case LeaveNotify:
		t = e->xcrossing.time;
		break;
	default:
		if(t_display_extension_sync && e->type == t_display_extension_sync_basep + XSyncAlarmNotify) {
			t = ((const XSyncAlarmNotifyEvent *)e)->time;
		}
		break;
	}
	return t;
}

static void event_set_curtime(XEvent *e)
{
	Time t = event_get_timestamp(e);
	if(t && event_last_user_time && event_time_after(event_last_user_time, t))
		event_reset_user_time();
	event_sourcetime = CurrentTime;
	event_curtime = t;
}

static void event_hack_mods(XEvent *e)
{
	switch (e->type) {
	case ButtonPress:
	case ButtonRelease:
		e->xbutton.state = t_keyboard_only_modmasks(e->xbutton.state);
		break;
	case KeyPress:
		break;
	case KeyRelease:
		break;
	case MotionNotify:
		e->xmotion.state = t_keyboard_only_modmasks(e->xmotion.state); {
			XEvent ce;
			struct wm_xqueue_window_type wt;
			wt.window = e->xmotion.window;
			wt.type = MotionNotify;
			while(xqueue_remove_local(&ce, xqueue_match_window_type, &wt)) {
				e->xmotion.x = ce.xmotion.x;
				e->xmotion.y = ce.xmotion.y;
				e->xmotion.x_root = ce.xmotion.x_root;
				e->xmotion.y_root = ce.xmotion.y_root;
			}
		}
		break;
	}
}

static void print_focusevent(XEvent *e)
{
	gint mode = e->xfocus.mode;
	gint detail = e->xfocus.detail;
	Window win = e->xany.window;
	const gchar *modestr, *detailstr;
	switch (mode) {
	case NotifyNormal:
		modestr = "NotifyNormal";
		break;
	case NotifyGrab:
		modestr = "NotifyGrab";
		break;
	case NotifyUngrab:
		modestr = "NotifyUngrab";
		break;
	case NotifyWhileGrabbed:
		modestr = "NotifyWhileGrabbed";
		break;
	default:
		g_assert_not_reached();
	}
	switch (detail) {
	case NotifyAncestor:
		detailstr = "NotifyAncestor";
		break;
	case NotifyVirtual:
		detailstr = "NotifyVirtual";
		break;
	case NotifyInferior:
		detailstr = "NotifyInferior";
		break;
	case NotifyNonlinear:
		detailstr = "NotifyNonlinear";
		break;
	case NotifyNonlinearVirtual:
		detailstr = "NotifyNonlinearVirtual";
		break;
	case NotifyPointer:
		detailstr = "Notifystruct wm_pointer";
		break;
	case NotifyPointerRoot:
		detailstr = "Notifystruct wm_pointerRoot";
		break;
	case NotifyDetailNone:
		detailstr = "NotifyDetailNone";
		break;
	default:
		g_assert_not_reached();
	}
	if(mode == NotifyGrab || mode == NotifyUngrab)
		return;
	g_assert(modestr);
	g_assert(detailstr);
	wm_debug_type(WM_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s", (e->xfocus.type == FocusIn ? "In" : "Out"), win,
		modestr, detailstr);
}

static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
{
	gint mode = e->xfocus.mode;
	gint detail = e->xfocus.detail;
	Window win = e->xany.window;
	if(e->type == FocusIn) {
		if(mode == NotifyGrab)
			return FALSE;
		if(mode == NotifyUngrab)
			return FALSE;
		if(win == DefaultRootWindow(t_display)) {
			if(in_client_only)
				return FALSE;
			else if(detail == NotifyPointerRoot || detail == NotifyDetailNone || detail == NotifyInferior
				|| detail == NotifyNonlinear)
				return TRUE;
			else
				return FALSE;
		}
		if(in_client_only) {
			struct wm_window *w = window_find(e->xfocus.window);
			if(!w || !WINDOW_IS_CLIENT(w))
				return FALSE;
		} else {
			if(detail == NotifyInferior)
				return TRUE;
		}
		if(detail == NotifyVirtual)
			return TRUE;
		if(detail == NotifyNonlinearVirtual)
			return TRUE;
		return FALSE;
	} else {
		g_assert(e->type == FocusOut);
		if(mode == NotifyGrab)
			return FALSE;
		if(mode == NotifyUngrab)
			return FALSE;
		if(win == DefaultRootWindow(t_display))
			return FALSE;
		if(detail == NotifyVirtual)
			return TRUE;
		if(detail == NotifyNonlinearVirtual)
			return TRUE;
		return FALSE;
	}
}

static gboolean event_look_for_focusin(XEvent *e, gpointer data)
{
	(void)data;
	return e->type == FocusIn && wanted_focusevent(e, FALSE);
}

static gboolean event_look_for_focusin_client(XEvent *e, gpointer data)
{
	(void)data;
	return e->type == FocusIn && wanted_focusevent(e, TRUE);
}

static gboolean is_enter_focus_event_ignored(gulong serial)
{
	GSList *it, *next;
	for(it = ignore_serials; it; it = next) {
		struct serial_range *r = it->data;
		next = g_slist_next(it);
		if((glong)(serial - r->end) > 0) {
			ignore_serials = g_slist_delete_link(ignore_serials, it);
			g_slice_free(struct serial_range, r);
		} else if((glong)(serial - r->start) >= 0)
			return TRUE;
	}
	return FALSE;
}

static gboolean unfocus_delay_func(gpointer data)
{
	struct focus_delay_data *d = data;
	Time old = event_curtime;
	event_curtime = d->time;
	event_curserial = d->serial;
	focus_nothing();
	event_curtime = old;
	return FALSE;
}

static void unfocus_delay_dest(gpointer data)
{
	g_slice_free(struct focus_delay_data, data);
	unfocus_delay_timeout_id = 0;
	unfocus_delay_timeout_client = NULL;
}

static void leave_client(struct wm_client *client)
{
	g_assert(config_focus_follow);
	if(is_enter_focus_event_ignored(event_curserial)) {
		wm_debug_type(WM_DEBUG_FOCUS, "Ignoring leave event with serial %lu\n on client 0x%x", event_curserial,
			client->window);
		return;
	}
	if(client == focus_client) {
		if(config_focus_delay) {
			struct focus_delay_data *data;
			if(unfocus_delay_timeout_id)
				g_source_remove(unfocus_delay_timeout_id);
			data = g_slice_new(struct focus_delay_data);
			data->client = client;
			data->time = event_time();
			data->serial = event_curserial;
			unfocus_delay_timeout_id =
				g_timeout_add_full(G_PRIORITY_DEFAULT, config_focus_delay, unfocus_delay_func, data, unfocus_delay_dest);
			unfocus_delay_timeout_client = client;
		} else {
			struct focus_delay_data data;
			data.client = client;
			data.time = event_time();
			data.serial = event_curserial;
			unfocus_delay_func(&data);
		}
	}
}

static gboolean skip_property_change_func(XEvent *e, gpointer data)
{
	const struct skip_property_change s = *(struct skip_property_change *)data;
	if(e->type == PropertyNotify && e->xproperty.window == s.window) {
		const Atom a = e->xproperty.atom;
		const Atom b = s.prop;
		if((a == T_PROP_ATOM(WM_NAME) || a == T_PROP_ATOM(WM_ICON_NAME))
				&& (b == T_PROP_ATOM(WM_NAME)
				|| b == T_PROP_ATOM(WM_ICON_NAME))) {
			return TRUE;
		} else if(a == b)
			return TRUE;
	}
	return FALSE;
}

static void event_handle_client(struct wm_client *client, XEvent *e)
{
	Atom msgtype;
	enum wm_frame_context con;
	static gint px = -1, py = -1;
	static guint pb = 0;
	static enum wm_frame_context pcon = WM_FRAME_CONTEXT_NONE;
	switch (e->type) {
	case ButtonPress:
		if(!pb) {
			pb = e->xbutton.button;
			px = e->xbutton.x;
			py = e->xbutton.y;
			pcon = frame_context(client, e->xbutton.window, px, py);
			pcon = mouse_button_frame_context(pcon, e->xbutton.button, e->xbutton.state);
		}
	case ButtonRelease:
		if(!(e->xbutton.button == 4 || e->xbutton.button == 5) && !grab_on_keyboard()) {
			con = frame_context(client, e->xbutton.window, px, py);
			con = mouse_button_frame_context(con, e->xbutton.button, e->xbutton.state);
			if((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) && e->xbutton.button == pb)
				pb = 0, px = py = -1, pcon = WM_FRAME_CONTEXT_NONE;
		}
		break;
	case MotionNotify:
		if(grab_on_pointer())
			break;
		con = frame_context(client, e->xmotion.window, e->xmotion.x, e->xmotion.y);
		break;
	case LeaveNotify:
		con = frame_context(client, e->xcrossing.window, e->xcrossing.x, e->xcrossing.y);
		switch (con) {
		case WM_FRAME_CONTEXT_FRAME:
			wm_debug_type(WM_DEBUG_FOCUS, "%sNotify mode %d detail %d on %lx",
				(e->type == EnterNotify ? "Enter" : "Leave"), e->xcrossing.mode, e->xcrossing.detail,
				(client ? client->window : 0));
			if(grab_on_keyboard())
				break;
			if(config_focus_follow && e->xcrossing.detail != NotifyInferior) {
				if(config_focus_delay && focus_delay_timeout_id)
					g_source_remove(focus_delay_timeout_id);
				if(config_unfocus_leave)
					leave_client(client);
			}
			break;
		default:
			break;
		}
		break;
	case EnterNotify:{
			con = frame_context(client, e->xcrossing.window, e->xcrossing.x, e->xcrossing.y);
			switch (con) {
			case WM_FRAME_CONTEXT_FRAME:
				if(grab_on_keyboard())
					break;
				if(e->xcrossing.mode == NotifyGrab || (e->xcrossing.mode == NotifyUngrab && !(config_focus_follow
							&& config_focus_under_mouse)) || e->xcrossing.detail == NotifyInferior) {
					wm_debug_type(WM_DEBUG_FOCUS, "%sNotify mode %d detail %d serial %lu on %lx IGNORED",
						(e->type == EnterNotify ? "Enter" : "Leave"), e->xcrossing.mode, e->xcrossing.detail,
						e->xcrossing.serial, client ? client->window : 0);
				} else {
					wm_debug_type(WM_DEBUG_FOCUS, "%sNotify mode %d detail %d serial %lu on %lx, focusing window",
						(e->type == EnterNotify ? "Enter" : "Leave"), e->xcrossing.mode, e->xcrossing.detail,
						e->xcrossing.serial, (client ? client->window : 0));
					if(config_focus_follow) {
						if(config_focus_delay && unfocus_delay_timeout_id)
							g_source_remove(unfocus_delay_timeout_id);
						event_enter_client(client);
					}
				}
				break;
			default:
				break;
			}
			break;
		}
	case ConfigureRequest:{
			gint x, y, w, h;
			gboolean move = FALSE;
			gboolean resize = FALSE;
			RECT_TO_DIMS(client->area, x, y, w, h);
			wm_debug("ConfigureRequest for \"%s\" desktop %d wm_state %d visible %d", client->title, screen_desktop,
				client->wm_state, client->frame->visible);
			wm_debug("                     x %d y %d w %d h %d b %d", x, y, w, h, client->border_width);
			if(e->xconfigurerequest.value_mask & CWBorderWidth)
				if(client->border_width != e->xconfigurerequest.border_width) {
					client->border_width = e->xconfigurerequest.border_width;
					move = TRUE;
				}
			if(e->xconfigurerequest.value_mask & CWStackMode) {
				struct wm_client *sibling = NULL;
				gulong ignore_start;
				gboolean ok = TRUE;
				if(e->xconfigurerequest.value_mask & CWSibling) {
					struct wm_window *win;
					win = window_find(e->xconfigurerequest.above);
					if(win && WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client) {
						sibling = WINDOW_AS_CLIENT(win);
					} else
						ok = FALSE;
				}
				if(ok) {
					if(!config_focus_under_mouse)
						ignore_start = event_start_ignore_all_enters();
					stacking_restack_request(client, sibling, e->xconfigurerequest.detail);
					if(!config_focus_under_mouse)
						event_end_ignore_all_enters(ignore_start);
				}
				move = TRUE;
			}
			if((e->xconfigurerequest.value_mask & CWX) || (e->xconfigurerequest.value_mask & CWY)
				|| (e->xconfigurerequest.value_mask & CWWidth) || (e->xconfigurerequest.value_mask & CWHeight)) {
				if(e->xconfigurerequest.value_mask & CWX) {
					x = e->xconfigurerequest.x;
					move = TRUE;
				}
				if(e->xconfigurerequest.value_mask & CWY) {
					y = e->xconfigurerequest.y;
					move = TRUE;
				}
				if(e->xconfigurerequest.value_mask & CWWidth) {
					w = e->xconfigurerequest.width;
					resize = TRUE;
				}
				if(e->xconfigurerequest.value_mask & CWHeight) {
					h = e->xconfigurerequest.height;
					resize = TRUE;
				}
			}
			wm_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d " "move %d resize %d",
				e->xconfigurerequest.value_mask & CWX, x, e->xconfigurerequest.value_mask & CWY, y,
				e->xconfigurerequest.value_mask & CWWidth, w, e->xconfigurerequest.value_mask & CWHeight, h, move, resize);
			if(x != client->area.x && x == (client->frame->area.x + client->frame->size.left - (gint)client->border_width)
				&& y != client->area.y && y == (client->frame->area.y + client->frame->size.top - (gint)client->border_width)
				&& w == client->area.width && h == client->area.height) {
				wm_debug_type(WM_DEBUG_APP_BUGS,
					"Application %s is trying to move via ConfigureRequest to it's root window position "
					"but it is not using StaticGravity", client->title);
				x = client->area.x;
				y = client->area.y;
			}
			if(x == 0 && y == 0 && client->gravity == NorthWestGravity && client_normal(client)) {
				const struct wm_rect to = { x, y, w, h };
				if(!client_is_oldfullscreen(client, &to)) {
					struct wm_rect *r;
					r = screen_area(client->desktop, SCREEN_AREA_ALL_MONITORS, NULL);
					if(r->x || r->y) {
						x = r->x;
						y = r->y;
						wm_debug_type(WM_DEBUG_APP_BUGS,
							"Application %s is trying to move via ConfigureRequest to 0,0 using "
							"NorthWestGravity, while there is a strut there. Moving buggy app from (0,0) to (%d,%d)",
							client->title, r->x, r->y);
					}
					g_slice_free(struct wm_rect, r);
				}
			}
			{
				gint lw, lh;
				client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
				if((e->xconfigurerequest.value_mask & CWWidth && !(e->xconfigurerequest.value_mask & CWX)))
					client_gravity_resize_w(client, &x, client->area.width, w);
				if((e->xconfigurerequest.value_mask & CWHeight && !(e->xconfigurerequest.value_mask & CWY)))
					client_gravity_resize_h(client, &y, client->area.height, h);
				client_find_onscreen(client, &x, &y, w, h, FALSE);
				wm_debug("Granting ConfigureRequest x %d y %d w %d h %d", x, y, w, h);
				client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
			}
			break;
		}
	case UnmapNotify:
		wm_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d ignores left %d", client->window,
			e->xunmap.event, e->xunmap.from_configure, client->ignore_unmaps);
		if(client->ignore_unmaps) {
			client->ignore_unmaps--;
			break;
		}
		client_unmanage(client);
		break;
	case DestroyNotify:
		wm_debug("DestroyNotify for window 0x%x", client->window);
		client_unmanage(client);
		break;
	case ReparentNotify:
		if(e->xreparent.parent == client->frame->window)
			break;
		wm_debug("ReparentNotify for window 0x%x", client->window);
		client_unmanage(client);
		break;
	case MapRequest:
		wm_debug("MapRequest for 0x%lx", client->window);
		break;
	case PropertyNotify:
		if(!client_validate(client))
			break;
		msgtype = e->xproperty.atom; {
			struct skip_property_change s;
			s.window = client->window;
			s.prop = msgtype;
			if(xqueue_exists_local(skip_property_change_func, &s))
				break;
		}
		msgtype = e->xproperty.atom;
		if(msgtype == XA_WM_NORMAL_HINTS) {
			int x, y, w, h, lw, lh;
			wm_debug("Update NORMAL hints");
			client_update_normal_hints(client);
			x = client->area.x;
			y = client->area.y;
			w = client->area.width;
			h = client->area.height;
			client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
			client_find_onscreen(client, &x, &y, w, h, FALSE);
			client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
		} else if(msgtype == T_PROP_ATOM(MOTIF_WM_HINTS)) {
			client_get_type_and_transientness(client);
		} else if(msgtype == XA_WM_HINTS) {
			client_update_wmhints(client);
		} else if(msgtype == XA_WM_TRANSIENT_FOR) {
			client_get_type_and_transientness(client);
			client_update_transient_for(client);
			client_calc_layer(client);
		} else if(msgtype == T_PROP_ATOM(WM_NAME)
			|| msgtype == T_PROP_ATOM(WM_ICON_NAME)) {
			client_update_title(client);
		} else if(msgtype == T_PROP_ATOM(WM_PROTOCOLS)) {
			client_update_protocols(client);
		} else if(msgtype == T_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) {
			if(moveresize_client == client)
				moveresize_end(FALSE);
			client_update_sync_request_counter(client);
		}
		break;
	case ColormapNotify:
		client_update_colormap(client, e->xcolormap.colormap);
		break;
	default:; {
			int kind;
			if(t_display_extension_shape && e->type == t_display_extension_shape_basep) {
				switch (((XShapeEvent *) e)->kind) {
				case ShapeBounding:
				case ShapeClip:
					client->shaped = ((XShapeEvent *) e)->shaped;
					kind = ShapeBounding;
					break;
				case ShapeInput:
					client->shaped_input = ((XShapeEvent *) e)->shaped;
					kind = ShapeInput;
					break;
				default:
					g_assert_not_reached();
				}
				frame_adjust_shape_kind(client->frame, kind);
			}
		}
	}
}

static void event_handle_root(XEvent *e)
{
	switch (e->type) {
	case SelectionClear:
		wm_debug("Another WM has requested to replace us. Exiting.");
		wm_exit_replace();
		break;
	case ConfigureNotify:
		XRRUpdateConfiguration(e);
		screen_resize();
		break;
	default:;
	}
}

static gboolean event_handle_user_input(struct wm_client *client, XEvent *e)
{
	g_assert(e->type == ButtonPress || e->type == ButtonRelease || e->type == MotionNotify || e->type == KeyPress
		|| e->type == KeyRelease);
	if(actions_interactive_input_event(e) || moveresize_event(e))
		return TRUE;
	if(moveresize_in_progress)
		client = moveresize_client;
	if(e->type == ButtonPress || e->type == ButtonRelease || e->type == MotionNotify) {
		return mouse_event(client, e);
	} else
		return keyboard_event((focus_cycle_target ? focus_cycle_target : (client ? client : focus_client)), e);
	return FALSE;
}

static void event_process(const XEvent *ec, gpointer data)
{
	XEvent ee, *e;
	Window window;
	struct wm_client *client = NULL;
	struct wm_window *wm_win = NULL;
	gboolean used;
	(void)data;
	ee = *ec;
	e = &ee;
	window = event_get_window(e);
	if(window != DefaultRootWindow(t_display) && (wm_win = window_find(window))) {
		switch (wm_win->type) {
		case WM_WINDOW_CLASS_CLIENT:
			client = WINDOW_AS_CLIENT(wm_win);
			break;
		case WM_WINDOW_CLASS_INTERNAL:
			break;
		}
	}
	event_set_curtime(e);
	event_curserial = e->xany.serial;
	event_hack_mods(e);
	if(e->type == FocusIn) {
		print_focusevent(e);
		if(!wanted_focusevent(e, FALSE)) {
			if(waiting_for_focusin) {
				wm_debug_type(WM_DEBUG_FOCUS, "Focus went to an unmanaged window 0x%x !", e->xfocus.window);
				focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE);
			}
		} else if(client && e->xfocus.detail == NotifyInferior) {
			wm_debug_type(WM_DEBUG_FOCUS, "Focus went to the frame window");
			focus_left_screen = FALSE;
			focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
			frame_adjust_focus(client->frame, FALSE);
		} else if(e->xfocus.detail == NotifyPointerRoot || e->xfocus.detail == NotifyDetailNone
			|| e->xfocus.detail == NotifyInferior || e->xfocus.detail == NotifyNonlinear) {
			wm_debug_type(WM_DEBUG_FOCUS, "Focus went to root or pointer root/none");
			if(e->xfocus.detail == NotifyInferior || e->xfocus.detail == NotifyNonlinear) {
				focus_left_screen = FALSE;
			}
			if(xqueue_exists_local(event_look_for_focusin_client, NULL)) {
				wm_debug_type(WM_DEBUG_FOCUS, "  but another FocusIn is coming");
			} else {
				if(!focus_left_screen)
					focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
			}
		} else if(!client) {
			wm_debug_type(WM_DEBUG_FOCUS, "Focus went to a window that is already gone");
			focus_set_client(NULL);
		} else if(client != focus_client) {
			focus_left_screen = FALSE;
			frame_adjust_focus(client->frame, TRUE);
			focus_set_client(client);
			client_calc_layer(client);
			client_bring_helper_windows(client);
		}
		waiting_for_focusin = FALSE;
	} else if(e->type == FocusOut) {
		print_focusevent(e);
		if(!wanted_focusevent(e, FALSE));
		else if(!xqueue_exists_local(event_look_for_focusin, NULL)) {
			Window win, root;
			gint i;
			guint u;
			t_display_ignore_errors(TRUE);
			if(XGetInputFocus(t_display, &win, &i) && XGetGeometry(t_display, win, &root, &i, &i, &u, &u, &u, &u)
				&& root != DefaultRootWindow(t_display)) {
				wm_debug_type(WM_DEBUG_FOCUS, "Focus went to another screen !");
				focus_left_screen = TRUE;
			} else
				wm_debug_type(WM_DEBUG_FOCUS, "Focus went to a black hole !");
			t_display_ignore_errors(FALSE);
			focus_set_client(NULL);
		} else {
			waiting_for_focusin = TRUE;
			focus_set_client(NULL);
		}
		if(client && client != focus_client)
			frame_adjust_focus(client->frame, FALSE);
	} else if(client)
		event_handle_client(client, e);
	else if(window == DefaultRootWindow(t_display))
		event_handle_root(e);
	else if(e->type == MapRequest)
		window_manage(window);
	else if(e->type == MappingNotify) {
		wm_debug("Keyboard map changed. Reloading keyboard bindings.");
		wm_set_state(WM_STATE_RECONFIGURING);
		t_keyboard_reload();
		keyboard_rebind();
		wm_set_state(WM_STATE_RUNNING);
	} else if(e->type == ConfigureRequest) {
		XWindowChanges xwc;
		xwc.x = e->xconfigurerequest.x;
		xwc.y = e->xconfigurerequest.y;
		xwc.width = e->xconfigurerequest.width;
		xwc.height = e->xconfigurerequest.height;
		xwc.border_width = e->xconfigurerequest.border_width;
		xwc.sibling = e->xconfigurerequest.above;
		xwc.stack_mode = e->xconfigurerequest.detail;
		t_display_ignore_errors(TRUE);
		XConfigureWindow(t_display, window, e->xconfigurerequest.value_mask, &xwc);
		t_display_ignore_errors(FALSE);
	} else if(t_display_extension_sync && e->type == t_display_extension_sync_basep + XSyncAlarmNotify) {
		XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent *) e;
		if(se->alarm == moveresize_alarm && moveresize_in_progress)
			moveresize_event(e);
	}
	if(e->type == ButtonPress || e->type == ButtonRelease) {
		struct wm_window *w;
		static guint pressed = 0;
		static Window pressed_win = None;
		if(window != DefaultRootWindow(t_display) || e->xbutton.subwindow == None || pressed == e->xbutton.button
			|| ((w = window_find(e->xbutton.subwindow)) && WINDOW_IS_INTERNAL(w))) {
			used = event_handle_user_input(client, e);
			if(e->type == ButtonPress) {
				pressed = e->xbutton.button;
				pressed_win = e->xbutton.subwindow;
			}
		}
	} else if(e->type == KeyPress || e->type == KeyRelease || e->type == MotionNotify) {
		used = event_handle_user_input(client, e);
	}
	event_curtime = event_sourcetime = CurrentTime;
	event_curserial = 0;
}

static void focus_delay_client_dest(struct wm_client *client, gpointer data)
{
	(void)data;
	if(focus_delay_timeout_client == client && focus_delay_timeout_id)
		g_source_remove(focus_delay_timeout_id);
	if(unfocus_delay_timeout_client == client && unfocus_delay_timeout_id)
		g_source_remove(unfocus_delay_timeout_id);
}

void event_startup(void)
{
	xqueue_add_callback(event_process, NULL);
	client_add_destroy_notify(focus_delay_client_dest, NULL);
}

void event_shutdown(void)
{
	client_remove_destroy_notify(focus_delay_client_dest);
}

static gboolean focus_delay_func(gpointer data)
{
	struct focus_delay_data *d = data;
	Time old = event_curtime;
	event_curtime = d->time;
	event_curserial = d->serial;
	if(client_focus(d->client) && config_focus_raise)
		stacking_raise(CLIENT_AS_WINDOW(d->client));
	event_curtime = old;
	return FALSE;
}

static void focus_delay_dest(gpointer data)
{
	g_slice_free(struct focus_delay_data, data);
	focus_delay_timeout_id = 0;
	focus_delay_timeout_client = NULL;
}

void event_enter_client(struct wm_client *client)
{
	g_assert(config_focus_follow);
	if(is_enter_focus_event_ignored(event_curserial)) {
		wm_debug_type(WM_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n on client 0x%x", event_curserial,
			client->window);
		return;
	}
	if(client_enter_focusable(client) && client_can_focus(client)) {
		if(config_focus_delay) {
			struct focus_delay_data *data;
			if(focus_delay_timeout_id)
				g_source_remove(focus_delay_timeout_id);
			data = g_slice_new(struct focus_delay_data);
			data->client = client;
			data->time = event_time();
			data->serial = event_curserial;
			focus_delay_timeout_id =
				g_timeout_add_full(G_PRIORITY_DEFAULT, config_focus_delay, focus_delay_func, data, focus_delay_dest);
			focus_delay_timeout_client = client;
		} else {
			struct focus_delay_data data;
			data.client = client;
			data.time = event_time();
			data.serial = event_curserial;
			focus_delay_func(&data);
		}
	}
}

static void event_ignore_enter_range(gulong start, gulong end)
{
	struct serial_range *r;
	g_assert(start != 0);
	g_assert(end != 0);
	r = g_slice_new(struct serial_range);
	r->start = start;
	r->end = end;
	ignore_serials = g_slist_prepend(ignore_serials, r);
	wm_debug_type(WM_DEBUG_FOCUS, "ignoring enters from %lu until %lu", r->start, r->end);
	T_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
}

void event_halt_focus_delay(void)
{
	if(event_curserial)
		event_ignore_enter_range(1, event_curserial);
	if(focus_delay_timeout_id)
		g_source_remove(focus_delay_timeout_id);
	if(unfocus_delay_timeout_id)
		g_source_remove(unfocus_delay_timeout_id);
}

gulong event_start_ignore_all_enters(void)
{
	return NextRequest(t_display);
}

void event_end_ignore_all_enters(gulong start)
{
	event_ignore_enter_range(start, NextRequest(t_display) - 1);
}

gboolean event_time_after(guint32 t1, guint32 t2)
{
	g_assert(t1 != CurrentTime);
	g_assert(t2 != CurrentTime);
#define TIME_HALF (guint32)(1 << 31)
	if(t2 >= TIME_HALF)
		return t1 >= t2 || t1 < (t2 + TIME_HALF);
	else
		return t1 >= t2 && t1 < (t2 + TIME_HALF);
}

gboolean find_timestamp(XEvent *e, gpointer data)
{
	const Time t = event_get_timestamp(e);
	(void)data;
	if(t && t >= event_curtime) {
		event_curtime = t;
		return TRUE;
	} else
		return FALSE;
}

static Time next_time(void)
{
	XChangeProperty(t_display, screen_support_win, T_PROP_ATOM(WM_CLASS), T_PROP_ATOM(STRING), 8,
		PropModeAppend, NULL, 0);
	xqueue_exists(find_timestamp, NULL);
	return event_curtime;
}

Time event_time(void)
{
	if(event_curtime)
		return event_curtime;
	return next_time();
}

Time event_source_time(void)
{
	return event_sourcetime;
}

void event_reset_time(void)
{
	next_time();
}

void event_update_user_time(void)
{
	event_last_user_time = event_time();
}

void event_reset_user_time(void)
{
	event_last_user_time = CurrentTime;
}
